]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
Replace Dash application display with AppWell
authorColin Walters <walters@verbum.org>
Tue, 30 Jun 2009 20:35:39 +0000 (16:35 -0400)
committerColin Walters <walters@verbum.org>
Tue, 30 Jun 2009 20:41:54 +0000 (16:41 -0400)
The new class AppWell implements the application favorite well
in the Dash component.  The previous AppDisplay remains for use
in the More... mode now.

Delete DEFAULT_APPLICATIONS; this is now in GConf.

Rename getMostUsedApps to getTopApps since we now have the
idea of explicit favorites.

Delete some GenericDisplay-related calls from overlay related
to the seletion - we'll reimplement keyboard nav in a more
coherent way later.

js/misc/appInfo.js
js/ui/appDisplay.js
js/ui/overlay.js
js/ui/widget.js

index 32f9bd0cf17fd55e0cfc0f01ec5e118014cf07ae..9f79ce625f60842175204c5c798450f24d558525 100644 (file)
@@ -7,32 +7,6 @@ const Shell = imports.gi.Shell;
 
 const Main = imports.ui.main;
 
-// TODO - move this into GConf once we're not a plugin anymore
-// but have taken over metacity
-// This list is taken from GNOME Online popular applications
-// http://online.gnome.org/applications
-// but with nautilus removed (since it should already be running)
-// and evince, totem, and gnome-file-roller removed (since they're
-// usually started by opening documents, not by opening the app
-// directly)
-const DEFAULT_APPLICATIONS = [
-    'mozilla-firefox.desktop',
-    'gnome-terminal.desktop',
-    'evolution.desktop',
-    'gedit.desktop',
-    'mozilla-thunderbird.desktop',
-    'rhythmbox.desktop',
-    'epiphany.desktop',
-    'xchat.desktop',
-    'openoffice.org-1.9-writer.desktop',
-    'emacs.desktop',
-    'gnome-system-monitor.desktop',
-    'openoffice.org-1.9-calc.desktop',
-    'eclipse.desktop',
-    'openoffice.org-1.9-impress.desktop',
-    'vncviewer.desktop'
-];
-
 function AppInfo(appId) {
     this._init(appId);
 }
@@ -41,8 +15,9 @@ AppInfo.prototype = {
     _init : function(appId) {
         this.appId = appId;
         this._gAppInfo = Gio.DesktopAppInfo.new(appId);
-        if (!this._gAppInfo)
+        if (!this._gAppInfo) {
             throw new Error('Unknown appId ' + appId);
+        }
 
         this.id = this._gAppInfo.get_id();
         this.name = this._gAppInfo.get_name();
@@ -96,40 +71,64 @@ function getAppInfo(appId) {
     return info;
 }
 
-// getMostUsedApps:
+// getTopApps:
 // @count: maximum number of apps to retrieve
 //
 // Gets a list of #AppInfos for the @count most-frequently-used
-// applications
+// applications, with explicitly-chosen favorites first.
 //
 // Return value: the list of #AppInfo
-function getMostUsedApps(count) {
+function getTopApps(count) {
     let appMonitor = Shell.AppMonitor.get_default();
 
+    let matches = [], alreadyAdded = {};
+
+    let favs = getFavorites();
+    for (let i = 0; i < favs.length && favs.length <= count; i++) {
+        let appId = favs[i].appId;
+
+        if (alreadyAdded[appId])
+            continue;
+        alreadyAdded[appId] = true;
+
+        matches.push(favs[i]);
+    }
+
     // Ask for more apps than we need, since the list of recently used
     // apps might contain an app we don't have a desktop file for
     let apps = appMonitor.get_most_used_apps (0, Math.round(count * 1.5));
-    let matches = [], alreadyAdded = {};
-
     for (let i = 0; i < apps.length && matches.length <= count; i++) {
         let appId = apps[i] + ".desktop";
+        if (alreadyAdded[appId])
+            continue;
+        alreadyAdded[appId] = true;
         let appInfo = getAppInfo(appId);
         if (appInfo) {
             matches.push(appInfo);
-            alreadyAdded[appId] = true;
         }
     }
 
-    // Fill the list with default applications it's not full yet
-    for (let i = 0; i < DEFAULT_APPLICATIONS.length && matches.length <= count; i++) {
-        let appId = DEFAULT_APPLICATIONS[i];
-        if (alreadyAdded[appId])
-            continue;
+    return matches;
+}
 
-        let appInfo = getAppInfo(appId);
-        if (appInfo)
-            matches.push(appInfo);
+function _idListToInfos(ids) {
+    let infos = [];
+    for (let i = 0; i < ids.length; i++) {
+        let display = getAppInfo(ids[i]);
+        if (display == null)
+            continue;
+        infos.push(display);
     }
+    return infos;
+}
 
-    return matches;
+function getFavorites() {
+    let system = Shell.AppSystem.get_default();
+
+    return _idListToInfos(system.get_favorites());
+}
+
+function getRunning() {
+    let monitor = Shell.AppMonitor.get_default();
+    return _idListToInfos(monitor.get_running_app_ids());
 }
index 889fa5366c608fbdba627140ba62712a770ed3c3..b2ef0a0e0091d0b51825ee4c0a4edda78cf23c82 100644 (file)
@@ -5,16 +5,21 @@ const Clutter = imports.gi.Clutter;
 const Pango = imports.gi.Pango;
 const Gio = imports.gi.Gio;
 const Gtk = imports.gi.Gtk;
+const Tidy = imports.gi.Tidy;
 const Shell = imports.gi.Shell;
 const Lang = imports.lang;
 const Signals = imports.signals;
+const Mainloop = imports.mainloop;
 
 const AppInfo = imports.misc.appInfo;
+const DND = imports.ui.dnd;
 const GenericDisplay = imports.ui.genericDisplay;
 
 const ENTERED_MENU_COLOR = new Clutter.Color();
 ENTERED_MENU_COLOR.from_pixel(0x00ff0022);
 
+const APP_ICON_SIZE = 48;
+
 const MENU_ICON_SIZE = 24;
 const MENU_SPACING = 15;
 
@@ -147,6 +152,7 @@ MenuItem.prototype = {
 }
 Signals.addSignalMethods(MenuItem.prototype);
 
+
 /* This class represents a display containing a collection of application items.
  * The applications are sorted based on their popularity by default, and based on
  * their name if some search filter is applied.
@@ -173,16 +179,15 @@ AppDisplay.prototype = {
         this._appMonitor = Shell.AppMonitor.get_default();
         this._appSystem = Shell.AppSystem.get_default();
         this._appsStale = true;
-        this._appSystem.connect('changed', Lang.bind(this, function(appSys) {
+        this._appSystem.connect('installed-changed', Lang.bind(this, function(appSys) {
             this._appsStale = true;
-            // We still need to determine what events other than search can trigger
-            // a change in the set of applications that are being shown while the
-            // user in in the overlay mode, however let's redisplay just in case.
             this._redisplay(false);
             this._redisplayMenus();
         }));
+        this._appSystem.connect('favorites-changed', Lang.bind(this, function(appSys) {
+            this._redisplay(false);
+        }));
         this._appMonitor.connect('changed', Lang.bind(this, function(monitor) {
-            this._appsStale = true;
             this._redisplay(false);
         }));
 
@@ -353,7 +358,7 @@ AppDisplay.prototype = {
 
     // Sets the list of the displayed items based on the most used apps.
     _setDefaultList : function() {
-        let matchedInfos = AppInfo.getMostUsedApps(MAX_ITEMS);
+        let matchedInfos = AppInfo.getTopApps(MAX_ITEMS);
         this._matchedItems = matchedInfos.map(function(info) { return info.appId; });
     },
 
@@ -430,3 +435,197 @@ AppDisplay.prototype = {
 };
 
 Signals.addSignalMethods(AppDisplay.prototype);
+
+function WellDisplayItem(appInfo, isFavorite) {
+    this._init(appInfo, isFavorite);
+}
+
+WellDisplayItem.prototype = {
+    _init : function(appInfo, isFavorite) {
+        this.appInfo = appInfo;
+
+        this.isFavorite = isFavorite;
+
+        this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
+                                   width: APP_ICON_SIZE,
+                                   reactive: true });
+        this.actor._delegate = this;
+        this.actor.connect('button-release-event', Lang.bind(this, function (b, e) {
+            this.launch();
+            this.emit('activated');
+        }));
+
+        let draggable = DND.makeDraggable(this.actor);
+
+        this._icon = appInfo.createIcon(APP_ICON_SIZE);
+
+        this.actor.append(this._icon, Big.BoxPackFlags.NONE);
+
+        let count = Shell.AppMonitor.get_default().get_window_count(appInfo.appId);
+
+        this._name = new Clutter.Text({ color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
+                                        font_name: "Sans 12px",
+                                        ellipsize: Pango.EllipsizeMode.END,
+                                        text: appInfo.name });
+        if (count > 0) {
+            let runningBox = new Big.Box({ /* border_color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
+                                           border: 1,
+                                           padding: 1 */ });
+            runningBox.append(this._name, Big.BoxPackFlags.EXPAND);
+            this.actor.append(runningBox, Big.BoxPackFlags.NONE);
+        } else {
+            this.actor.append(this._name, Big.BoxPackFlags.NONE);
+        }
+    },
+
+    // Opens an application represented by this display item.
+    launch : function() {
+        this.appInfo.launch();
+    },
+
+    // Draggable interface - FIXME deduplicate with GenericDisplay
+    getDragActor: function(stageX, stageY) {
+        this.dragActor = this.appInfo.createIcon(APP_ICON_SIZE);
+
+        // If the user dragged from the icon itself, then position
+        // the dragActor over the original icon. Otherwise center it
+        // around the pointer
+        let [iconX, iconY] = this._icon.get_transformed_position();
+        let [iconWidth, iconHeight] = this._icon.get_transformed_size();
+        if (stageX > iconX && stageX <= iconX + iconWidth &&
+            stageY > iconY && stageY <= iconY + iconHeight)
+            this.dragActor.set_position(iconX, iconY);
+        else
+            this.dragActor.set_position(stageX - this.dragActor.width / 2, stageY - this.dragActor.height / 2);
+        return this.dragActor;
+    },
+
+    // Returns the original icon that is being used as a source for the cloned texture
+    // that represents the item as it is being dragged.
+    getDragActorSource: function() {
+        return this._icon;
+    }
+};
+
+Signals.addSignalMethods(WellDisplayItem.prototype);
+
+function WellArea(width, isFavorite) {
+    this._init(width, isFavorite);
+}
+
+WellArea.prototype = {
+    _init : function(width, isFavorite) {
+        this.isFavorite = isFavorite;
+
+        this.actor = new Tidy.Grid({ width: width });
+        this.actor._delegate = this;
+    },
+
+    redisplay: function (infos) {
+        let children;
+
+        children = this.actor.get_children();
+        children.forEach(Lang.bind(this, function (v) {
+            v.destroy();
+        }));
+
+        for (let i = 0; i < infos.length; i++) {
+            let display = new WellDisplayItem(infos[i], this.isFavorite);
+            display.connect('activated', Lang.bind(this, function (display) {
+                this.emit('activated', display);
+            }));
+            this.actor.add_actor(display.actor);
+        };
+    },
+
+    // Draggable target interface
+    acceptDrop : function(source, actor, x, y, time) {
+        let global = Shell.Global.get();
+
+        if (!(source instanceof WellDisplayItem)) {
+            return false;
+        }
+
+        let appSystem = Shell.AppSystem.get_default();
+        let id = source.appInfo.appId;
+        if (source.isFavorite && (!this.isFavorite)) {
+            Mainloop.idle_add(function () {
+                appSystem.remove_favorite(id);
+            });
+        } else if ((!source.isFavorite) && this.isFavorite) {
+            Mainloop.idle_add(function () {
+                appSystem.add_favorite(id);
+            });
+        } else {
+            return false;
+        }
+
+        return true;
+    }
+}
+
+Signals.addSignalMethods(WellArea.prototype);
+
+function AppWell(width) {
+    this._init(width);
+}
+
+AppWell.prototype = {
+    _init : function(width) {
+        this._menus = [];
+        this._menuDisplays = [];
+
+        this.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
+                                   width: width });
+
+        this._appSystem = Shell.AppSystem.get_default();
+        this._appMonitor = Shell.AppMonitor.get_default();
+
+        this._appSystem.connect('installed-changed', Lang.bind(this, function(appSys) {
+            this._redisplay();
+        }));
+        this._appSystem.connect('favorites-changed', Lang.bind(this, function(appSys) {
+            this._redisplay();
+        }));
+        this._appMonitor.connect('changed', Lang.bind(this, function(monitor) {
+            this._redisplay();
+        }));
+
+        this._favoritesArea = new WellArea(width, true);
+        this._favoritesArea.connect('activated', Lang.bind(this, function (a, display) {
+            this.emit('activated');
+        }));
+        this.actor.append(this._favoritesArea.actor, Big.BoxPackFlags.NONE);
+
+        this._runningBox = new Big.Box({ border_color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
+                                         border: 1,
+                                         corner_radius: 3,
+                                         padding: GenericDisplay.PREVIEW_BOX_PADDING });
+        this._runningArea = new WellArea(width, false);
+        this._runningArea.connect('activated', Lang.bind(this, function (a, display) {
+            this.emit('activated');
+        }));
+        this._runningBox.append(this._runningArea.actor, Big.BoxPackFlags.EXPAND);
+        this.actor.append(this._runningBox, Big.BoxPackFlags.NONE);
+
+        this._redisplay();
+    },
+
+    _redisplay: function() {
+        let arrayToObject = function(a) {
+            let o = {};
+            for (let i = 0; i < a.length; i++)
+                o[a[i]] = 1;
+            return o;
+        };
+        let favorites = AppInfo.getFavorites();
+        let favoriteIds = arrayToObject(favorites.map(function (e) { return e.appId; }));
+        let running = AppInfo.getRunning().filter(function (e) {
+            return !(e.appId in favoriteIds);
+        });
+        this._favoritesArea.redisplay(favorites);
+        this._runningArea.redisplay(running);
+    }
+};
+
+Signals.addSignalMethods(AppWell.prototype);
index fdfda5a55a84e49446b0845da406a8e1ce007544..03078ff95511a1ee3c4b17610fbeb3b2307c3226 100644 (file)
@@ -283,7 +283,6 @@ Dash.prototype = {
         this._searchEntry.entry.connect('activate', function (se) {
             // only one of the displays will have an item selected, so it's ok to
             // call activateSelected() on all of them
-            me._appDisplay.activateSelected();
             me._docDisplay.activateSelected();
             me._resultsAppsSection.display.activateSelected();
             me._resultsDocsSection.display.activateSelected();
@@ -329,11 +328,9 @@ Dash.prototype = {
         
         this._appsContent = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
         this._appsSection.append(this._appsContent, Big.BoxPackFlags.EXPAND);
-        this._appDisplay = new AppDisplay.AppDisplay(this._displayWidth, this._itemDisplayHeight / 2, DASH_COLUMNS, DASH_SECTION_PADDING);
-        let sideArea = this._appDisplay.getSideArea();
-        sideArea.hide();
-        this._appsContent.append(sideArea, Big.BoxPackFlags.NONE);
-        this._appsContent.append(this._appDisplay.actor, Big.BoxPackFlags.EXPAND);
+        this._appWell = new AppDisplay.AppWell(this._displayWidth);
+        this._appWell.actor.show();
+        this._appsContent.append(this._appWell.actor, Big.BoxPackFlags.EXPAND);
 
         let moreAppsBox = new Big.Box({x_align: Big.BoxAlignment.END});
         this._moreAppsLink = new Link.Link({ color: DASH_TEXT_COLOR,
@@ -444,13 +441,12 @@ Dash.prototype = {
         let itemDetailsAvailableWidth = this._detailsWidth - DASH_SECTION_PADDING * 2 - DASH_BORDER_WIDTH * 2;
         let itemDetailsAvailableHeight = detailsHeight - DASH_SECTION_PADDING * 2 - DASH_BORDER_WIDTH * 2;
 
-        this._appDisplay.setAvailableDimensionsForItemDetails(itemDetailsAvailableWidth, itemDetailsAvailableHeight);
         this._docDisplay.setAvailableDimensionsForItemDetails(itemDetailsAvailableWidth, itemDetailsAvailableHeight);
         this._resultsAppsSection.display.setAvailableDimensionsForItemDetails(itemDetailsAvailableWidth, itemDetailsAvailableHeight);
         this._resultsDocsSection.display.setAvailableDimensionsForItemDetails(itemDetailsAvailableWidth, itemDetailsAvailableHeight);
 
         /* Proxy the activated signals */
-        this._appDisplay.connect('activated', function(appDisplay) {
+        this._appWell.connect('activated', function(well) {
             me.emit('activated');
         });
         this._docDisplay.connect('activated', function(docDisplay) {
@@ -462,27 +458,7 @@ Dash.prototype = {
         this._resultsDocsSection.display.connect('activated', function(resultsDocsDisplay) {
             me.emit('activated');
         });
-        this._appDisplay.connect('selected', function(appDisplay) {
-            // We allow clicking on any item to select it, so if an 
-            // item in the app display is selected, we need to make sure that
-            // no item in the doc display has the selection.
-            me._docDisplay.unsetSelected();
-            me._resultsDocsSection.display.unsetSelected();
-            me._resultsAppsSection.display.unsetSelected();
-            if (me._firstSelectAfterOverlayShow) {
-                me._firstSelectAfterOverlayShow = false;
-            } else if (!me._detailsShowing()) { 
-                me._detailsPane.show();
-                me.emit('panes-displayed');
-            }
-            me._detailsContent.remove_all();
-            me._detailsContent.append(me._appDisplay.selectedItemDetails, Big.BoxPackFlags.NONE); 
-        });
         this._docDisplay.connect('selected', function(docDisplay) {
-            // We allow clicking on any item to select it, so if an 
-            // item in the doc display is selected, we need to make sure that
-            // no item in the app display has the selection.
-            me._appDisplay.unsetSelected(); 
             me._resultsDocsSection.display.unsetSelected();
             me._resultsAppsSection.display.unsetSelected();
             if (!me._detailsShowing()) { 
@@ -493,7 +469,6 @@ Dash.prototype = {
             me._detailsContent.append(me._docDisplay.selectedItemDetails, Big.BoxPackFlags.NONE); 
         });
         this._resultsDocsSection.display.connect('selected', function(resultsDocDisplay) {
-            me._appDisplay.unsetSelected(); 
             me._docDisplay.unsetSelected();
             me._resultsAppsSection.display.unsetSelected();
             if (!me._detailsShowing()) { 
@@ -504,7 +479,6 @@ Dash.prototype = {
             me._detailsContent.append(me._resultsDocsSection.display.selectedItemDetails, Big.BoxPackFlags.NONE);
         });
         this._resultsAppsSection.display.connect('selected', function(resultsAppDisplay) {
-            me._appDisplay.unsetSelected(); 
             me._docDisplay.unsetSelected();
             me._resultsDocsSection.display.unsetSelected();
             if (!me._detailsShowing()) { 
@@ -514,12 +488,6 @@ Dash.prototype = {
             me._detailsContent.remove_all();
             me._detailsContent.append(me._resultsAppsSection.display.selectedItemDetails, Big.BoxPackFlags.NONE);
         });
-        this._appDisplay.connect('redisplayed', function(appDisplay) {
-            me._ensureItemSelected();
-        });
-        this._docDisplay.connect('redisplayed', function(docDisplay) {
-            me._ensureItemSelected();
-        });
 
         this._moreAppsLink.connect('clicked',
             function(o, event) {
@@ -543,7 +511,6 @@ Dash.prototype = {
     show: function() {
         let global = Shell.Global.get();
 
-        this._appDisplay.show();
         this._appsContent.show();
         this._docDisplay.show();
         global.stage.set_key_focus(this._searchEntry.entry);
@@ -567,22 +534,6 @@ Dash.prototype = {
         this._unsetSearchMode();
     },
 
-    // Ensures that one of the displays has the selection if neither owns it after the
-    // latest redisplay. This can be applicable if the display that earlier had the
-    // selection no longer has any items, or if their is a single section being shown 
-    // in the expanded view and it went from having no matching items to having some.
-    // We first try to place the selection in the applications section, because it is
-    // displayed above the documents section.
-    _ensureItemSelected: function() { 
-        if (!this._appDisplay.hasSelected() && !this._docDisplay.hasSelected()) {
-            if (this._appDisplay.hasItems()) { 
-                this._appDisplay.selectFirstItem();
-            } else if (this._docDisplay.hasItems()) {
-                this._docDisplay.selectFirstItem();
-            }
-        }
-    },
-
     // Sets the 'More' mode for browsing applications.
     _setMoreAppsMode: function() {
         if (this._moreAppsMode)
@@ -811,6 +762,7 @@ Overlay.prototype = {
     // the item on any workspace.
     handleDragOver : function(source, actor, x, y, time) {
         if (source instanceof GenericDisplay.GenericDisplayItem) {
+            log("unsetting more mode");
             this._dash.unsetMoreMode();
             return true;
         }
index a49db311738f400824a0bc431784affccde0e2d0..6e4c8d6c14bb683e19b903148d02f45232c43047 100644 (file)
@@ -284,7 +284,7 @@ AppsWidget.prototype = {
         this.actor = new Big.Box({ spacing: 2 });
         this.collapsedActor = new Big.Box({ spacing: 2});
 
-        let apps = AppInfo.getMostUsedApps(5);
+        let apps = AppInfo.getTopApps(5);
         for (let i = 0; i < apps.length; i++)
             this.addItem(apps[i]);
     }